iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0
Modern Web

Go,一起成為全端吧!—— 給前端工程師的 Golang 後端學習筆記系列 第 13

Day13 - Gin Middleware: API 錯誤處理方式

  • 分享至 

  • xImage
  •  

今天要來介紹 Go 的 Middleware!讓 To-do List API 錯誤回應更簡潔、更好管理~
一開始會先介紹一下 Middleware,然後再說明要怎麼修改 To-do List API。


Middleware 是什麼?

Middleware 就是「 在請求和回應的中間過程中,先做一些額外處理 」的函式,它叫做中介軟體或是中間件。

請求 → Middleware → Handler (API 邏輯) → Middleware → 回應

簡單來說,可以想成:
每個人進公司的時候,都會經過保全,保全確認你有停車證就會讓你通過。如果沒有停車證,就會檢查你的身份證,然後再簽名本上留下紀錄,才會讓你進去。

所以 Middleware 就很像是保全,基本上每個人的進出都需要經過,然後他會在你出入大門的時候,檢查你的身份,並且做紀錄。

👉 一種 攔截器 的概念!

也因為 Middleware 可以攔截請求、處理回應,所以它常被用來做以下功能:

  1. 請求紀錄 (Logging)
  2. 錯誤處理 (Error Handling)
  3. 身份驗證 (Authentication)
  4. 權限控管 (Authorization)
  5. 跨域資源共享 (CORS)
  6. Recovery(避免整個 server 掛掉)

Middleware 的用法:r.Use() 。位置則是放在 server 建立之後;router建立之前。
可以新增一個或多個 Middleware 到 r.Use() ,這樣每個請求就都會依序經過,然後再透過 c.Next() 決定要在 handler 前執行還是之後執行。

  • handler 前:log 記錄請求時間、解析 Token、檢查權限等。
  • handler 後:統一錯誤處理、計算耗時、回傳 log。
r := gin.Default()   // 放在 server 建立之後
 
// 寫法 1
r.Use(Middleware1, Middleware2, ...)
 
// 寫法 2
r.Use(Middleware1)
r.Use(Middleware2)

r.GET("/tasks", getTasks)  // 放在 router 建立之前

範例:

// Middleware function 範例
func ExampleMiddleware(c *gin.Context) {
    // 前置處理
    fmt.Println("Before handler")

    c.Next() // 執行真正的 handler

    // 後置處理
    fmt.Println("After handler")
}

透過 Middleware 的協助,我們就可以不用寫一堆重複的程式(Ex: 錯誤處理、身份驗證),可以讓程式結構更簡潔。

所以接下來就要來修改 To-do List API 的錯誤處理!


修改 To-do List API 錯誤處理

前幾天,我們已經完成了 GET、POST、PATCH、DELETE 的 API 設計,但不知道大家有沒有發現在這些函式之中,其實有一些重複的地方。

每一次呼叫 API 函式時,都會回應呼叫狀態是否有成功,而在回應 API 的錯誤訊息時,就會輸入以下內容:

c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})

而當這些錯誤訊息不斷重複出現時,就可以考慮把它移出來,請 Middleware 協助管理。這樣就可以透過傳入不同錯誤參數的方式,集中管理全部的錯誤訊息。

修改為:

c.Error("這邊帶錯誤參數")

首先,先來建立一個自訂錯誤的清單:

// 自訂錯誤
var (
	ErrNotFound  = errors.New("task not found")
	ErrInvalidID = errors.New("invalid ID")
)

這邊把錯誤訊息都改成小寫的原因是因為 Go 的官方程式設計慣例裡,有以下的規則:

錯誤訊息(error string)不應該以大寫字母開頭,也不應該以標點符號結尾。
除非這個錯誤字串裡面本身有專有名詞(像是 HTTP、ID、SQL 等等)。

再來是,建立 Middleware 的函式(ErrorHandler):


func ErrorHandler(c *gin.Context) {
	c.Next()                                         // 在 handler 之後執行

	if len(c.Errors) > 0 {                           // 檢查是否有錯誤
		err := c.Errors.Last().Err

		var statusCode int
		var message string

		switch {                                       // 判斷錯誤類型
			case errors.Is(err, ErrNotFound):
				statusCode = http.StatusNotFound
				message = err.Error()
				
			case errors.Is(err, ErrInvalidID):
				statusCode = http.StatusBadRequest
				message = err.Error()
				
			default:
				statusCode = http.StatusInternalServerError
				message = "internal server error"
		}

		c.JSON(statusCode, gin.H{"error": message})
		c.Abort()                                      // 中斷 Middleware(停止)
	}
}

完成 ErrorHandler 之後,在 main 函式裡補上 r.Use(ErrorHandler) ,然後再依序把原本程式中處理 error message 的地方改成:

c.Error(err)
c.Error(ErrInvalidID)
c.Error(ErrNotFound)

把剛剛自訂的錯誤參數帶進去,這樣就完成了! 🎉 

這樣 To-do List 也有了個雛形,但目前所有的程式都在 main.go 的檔案中,這樣其實不太符合實際開發的做法,所以明天會先介紹在實務中,一般會把專案拆成幾個層次。
那我們就明天見囉~ 👋👋👋


上一篇
Day12 - RESTful 設計:檢視 To-do List API 規劃
系列文
Go,一起成為全端吧!—— 給前端工程師的 Golang 後端學習筆記13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言